package com.andox.fn.inf.rpc.rest.protocol.registry;

import java.util.List;
import java.util.Map;

import com.andox.fn.inf.rpc.rest.cluster.loadbalance.ClusterInvoker;
import com.andox.fn.inf.rpc.rest.common.ObjectHolder;
import com.andox.fn.inf.rpc.rest.config.ReferenceConfig;
import com.andox.fn.inf.rpc.rest.config.ServiceConfig;
import com.andox.fn.inf.rpc.rest.extension.ExtensionLoader;
import com.andox.fn.inf.rpc.rest.filter.Invoker;
import com.andox.fn.inf.rpc.rest.protocol.Protocol;
import com.andox.fn.inf.rpc.rest.protocol.registry.RegistryService.RegistryNotify;
import com.andox.fn.inf.rpc.rest.util.InterfaceConfigUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public class RegistryProtocol implements Protocol{
	private RegistryService registryService = new DefaultRegistryService();
	
	private Map<Class<?>, RegistryNotify> notifyTable = Maps.newConcurrentMap();
	private Map<Class<?>, ObjectHolder<ClusterInvoker>> invokerTable = Maps.newConcurrentMap();
	
	@Override
	public Invoker refer(ReferenceConfig referenceConfig) {
		ClusterInvoker ci = this.lookupClusterInvoker(referenceConfig.getInterfaceClass(), referenceConfig);
		RegistryNotify rn = this.lookupRegistryNotify(referenceConfig.getInterfaceClass(), ci);
		
		List<Invoker> invokerList = Lists.newArrayList();
		ci.setInvokers(invokerList);
		
		List<ReferenceConfig> serviceConfigList = registryService.subscribe(referenceConfig, rn);
		for (ReferenceConfig sc : serviceConfigList) {
			ReferenceConfig rc = InterfaceConfigUtil.mergeServiceConfig((ReferenceConfig)ci.getConfig(), sc);
			Invoker invoker = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(sc.getProtocol()).refer(rc);
			invokerList.add(invoker);
		}
		
		ci.setInvokers(invokerList);
		return ci;
	}

	private RegistryNotify lookupRegistryNotify(Class<?> clazz, ClusterInvoker ci) {
		RegistryNotify rn = this.notifyTable.get(clazz);
		if (rn == null) {
			rn = new DefaultRegistryNotify(ci);
			this.notifyTable.put(clazz, rn);
		}
		
		return rn;
	}
	
	private ClusterInvoker lookupClusterInvoker(Class<?> clazz, ReferenceConfig referenceConfig) {
		ObjectHolder<ClusterInvoker> rn = this.invokerTable.get(clazz);
		if (rn == null) {
			synchronized (this) {
				if (this.invokerTable.get(clazz) == null) {
					rn = new ObjectHolder<ClusterInvoker>();
					rn.set(new ClusterInvoker(referenceConfig));
					List<Invoker> invokerList = Lists.newArrayList();
					rn.get().setInvokers(invokerList);
					this.invokerTable.put(clazz, rn);
				}
			}
		}
		
		return rn.get();
	}
	
	@Override
	public Invoker export(Invoker invoker) {
		ServiceConfig sc = (ServiceConfig)invoker.getConfig();
		registryService.register(sc);
		
		return invoker;
	}
	
	public static class DefaultRegistryNotify implements RegistryNotify{
		private ClusterInvoker clusterInvoker;
		
		DefaultRegistryNotify(ClusterInvoker ci){
			this.clusterInvoker = ci;
		}
		
		@Override
		public void notify(List<ReferenceConfig> currentList) {
			List<Invoker> invokerList = Lists.newArrayList();
			for (ReferenceConfig sc : currentList) {
				ReferenceConfig rc = InterfaceConfigUtil.mergeServiceConfig((ReferenceConfig)clusterInvoker.getConfig(), sc);
				Invoker invoker = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(sc.getProtocol()).refer(rc);
				invokerList.add(invoker);
			}
			
			clusterInvoker.setInvokers(invokerList);
		}
	}
}
